/*
 * @(#)XfvhdlRulesMemData.java        3.0                     2004/09/14
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfvhdl;

import xfuzzy.lang.*;
import java.io.*;

/**
* Clase que gestiona la estructura que contiene la informaci�n
* de la base de reglas.
* @author Jos� Mar�a �vila Maireles, <b>e-mail</b>: josavimai@alum.us.es
* @version 3.0
*/
public class XfvhdlRulesMemData {

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
	//		  ATRIBUTOS PRIVADOS DE LA CLASE 				        
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

	private Object vector[][]; // Estructura de datos  
	private int longitud; // N�mero de filas de la estructura
	private int N; // N�mero de bits con los que se codifica el valor
	private int K; // N�mero de bits con los que se codifica el peso
	private int bits_var; // N�mero de bits con los que se codifica 
	// una variable de entrada
	private Variable[] var_in; // Variables de entrada
	private Variable[] var_output; // Variables de salida
	// Funciones miembro de las variables de entrada ordenadas
	private XfvhdlInOrderParamMemFunc[] inOrderMfInput;
	// Funciones miembro de la variable de salida ordenadas
	private XfvhdlInOrderParamMemFunc inOrderMfOutput;
	private int mfUsed[]; // Funciones miembro usadas 
	private double val;

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
	//				  CONSTRUCTOR DE LA CLASE					   
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

	/**
	 * Constructor de XFvhdlRulesMemData
	 * Inicializa la estructura l�nea a l�nea a partir de los datos 
	 * obtenidos de la base de reglas.
	 * 
	 * NOTA: Cuando una l�nea de la estructura contiene datos la marca 
	 * como verdadero, y en caso contrario la marca como falso. Esto nos 
	 * vale a la hora de generar c�digo VHDL para RAM o para ROM.
	 */
	public XfvhdlRulesMemData(int filas, int n, int kk, Specification spec)
		throws IOException {

		// Calculamos la longitud de la estructura
		longitud = (int) Math.pow((double) 2, (double) filas);

		// Inicializamos la estructura
		vector = new Object[longitud][4];
		this.N = n;
		this.K = kk;

		// Obtengo las variables de entrada y salida de la base de reglas
		// OJO, supongo que s�lo tengo una base de reglas 
		// (en caso contrario genero un warning)
		KnowledgeBase[] rulebases = spec.getRulebases();

		if (rulebases.length > 1) {
			XfvhdlError err = new XfvhdlError();
			err.newWarning(9);
		}

		var_in = rulebases[0].getInputs();

		bits_var = filas / var_in.length;

		var_output = rulebases[0].getOutputs();

		// PARA N VARIABLES DE ENTRADA:

		// PASO 1: Creo la estructura inOrderMfInput[i], donde con el 
		//         �ndice i direcciono las distintas variables de entrada, 
		//         donde contengo una lista con sus funciones miembro 
		//         ordenadas.

		inOrderMfInput = new XfvhdlInOrderParamMemFunc[var_in.length];
		for (int i = 0; i < var_in.length; i++) {
			inOrderMfInput[i] = new XfvhdlInOrderParamMemFunc(var_in[i]);
			inOrderMfInput[i].sort();
		}

		// PASO 2: Guardo en inOrderMfOutput las funciones miembro 
		//         ordenadas de la variable de salida. 
		//         SOLO VALIDO PARA 1 VARIABLE DE SALIDA. 

		inOrderMfOutput = new XfvhdlInOrderParamMemFunc(var_output[0]);
		inOrderMfOutput.sort();

		// PASO 3: Guardo las reglas del sistema XFL en cprule.		 
		Rule[] cprule = null;
		RL_Rule[] cprulelist = null;
		if (rulebases[0] instanceof RuleSet) {
			cprule = ((RuleSet) rulebases[0]).getRules();
		}
		else if (rulebases[0] instanceof RuleList) {
			cprulelist = ((RuleList) rulebases[0]).getRules();
		}

		// PASO 4: Inicializo la estructura vector donde se guardar� el 
		//         contenido de la memoria de reglas. Inicializo todas 
		//         sus filas a false.
		//
		//         Estructura de vector:
		//         vector[_][0] ==> Guardo el n�mero de fila en binario.
		//         vector[_][1] ==> Guardo el valor de la mf que se activa 
		//                          en la salida.
		//         vector[_][2] ==> Guardo el peso.
		//         vector[_][3] ==> Indico si la fila es v�lida o no.

		XfvhdlBinaryDecimal converter = new XfvhdlBinaryDecimal();
		for (int i = 0; i < longitud; i++) {
			vector[i][0] = converter.toBinary(i, filas);
			vector[i][3] = new Boolean(false);
		}

		// PASO 5: Voy recorriendo las reglas y guardo su informaci�n en 
		// vector.
		mfUsed = new int[var_in.length];
		for (int k = 0; k < cprule.length; k++) {
			// Obtenemos el consecuente.
			Conclusion conc[] = null;
			if (rulebases[0] instanceof RuleSet) {
				conc = cprule[k].getConclusions();
			}
			else if (rulebases[0] instanceof RuleList) {
				conc = cprulelist[k].getConclusions();
			}
			if (conc.length != 1) {
				new XfvhdlError(13, "" + k);
			}

			// Inicializo mfUsed
			for (int i = 0; i < mfUsed.length; i++)
				mfUsed[i] = -1;

			Relation left = null;
			Relation right = null;

			// Nos aseguramos que la regla es de tipo AND o IS.
			
			Relation rl = null;
			if (rulebases[0] instanceof RuleSet) {
				rl = cprule[k].getPremise();
			}
			else if (rulebases[0] instanceof RuleList) {
				rl = cprulelist[k].getPremise();
			}
			if (rl.getKind() != Relation.AND && rl.getKind() != Relation.IS) {
				new XfvhdlError(10, "" + k);
				//err.show();
				return;
			} else if (rl.getKind() == Relation.IS) {
				left = rl;
			} else {
				// parte izquierda
				left = rl.getLeftRelation();

				// parte derecha
				right = rl.getRightRelation();
			}

			// calculo las mf usadas
			getMfUsed(left, right, k);

			// averiguo la conclusion
			int pos_c = 0;
			ParamMemFunc c = null;
			try {
			  c = (ParamMemFunc) conc[0].getMembershipFunction();
			} catch(Exception ex) {
			 new XfvhdlError(31);
			}
			// averiguo de que mf se trata dentro de la variable de salida 
			// Y LA GUARDO EN TMP
			boolean enc = false;
			ParamMemFunc tmp;
			for (kk = 0;
				kk < inOrderMfOutput.getInOrderParamMemFunc().size() && !enc;
				kk++) {
				tmp =
					(ParamMemFunc) inOrderMfOutput
						.getInOrderParamMemFunc()
						.get(
						kk);
				val = tmp.center();
				if (c != null && tmp.equals(c.getLabel())) {
					pos_c = kk;
					enc = true;
				}
			}

			// PASO 6: Guardo la informaci�n correspondiente en vector.			
			XfvhdlVectorCount v =
				new XfvhdlVectorCount(
					var_in.length,
					inOrderMfInput[0].getInOrderParamMemFunc().size() - 1,
					mfUsed);

			int tmp1, tmp2;
			String aux = new String();

			for (int tt = 0; tt < var_in.length; tt++) {
				tmp1 = v.get(tt);
				aux += converter.toBinary(tmp1, bits_var);
			}
			tmp2 = converter.toDecimal(aux);

			//comento esta l�nea porque lo que guarda es la mf que se
			//activa, y lo que necesito es su valor!!! 
			//vector[tmp2][1]=converter.toBinary(pos_c,N);

			//ahora si que estoy guardando su valor.
			//TODO: pero ojo, lo que guardo es siempre el centro
			vector[tmp2][1] =
				converter.toBinaryInRange(val, 0.0, 1.0, XfvhdlProperties.N);

			vector[tmp2][3] = new Boolean(true);
			// calculamos su peso:
			if (XfvhdlProperties.calculateWeights == true) {
				vector[tmp2][2] =
					converter.toBinaryInRange(
						calculateWeight(pos_c),
						0.0,
						1.0,
						XfvhdlProperties.K);
			}

			while (v.sub()) {
				aux = "";
				for (int tt = 0; tt < var_in.length; tt++) {
					tmp1 = v.get(tt);
					aux += converter.toBinary(tmp1, bits_var);
				}
				tmp2 = converter.toDecimal(aux);

				vector[tmp2][1] = converter.toBinary(pos_c, N);
				vector[tmp2][3] = new Boolean(true);
				// calculamos su peso:
				if (XfvhdlProperties.calculateWeights == true) {
					/* vieja version
					Double peso = new Double (calculateWeight(pos_c));
					vector[tmp2][2]=new String (peso.toString());
					*/
					vector[tmp2][2] =
						converter.toBinaryInRange(
							calculateWeight(pos_c),
							0.0,
							1.0,
							XfvhdlProperties.K);
				}
			}
		}
	}

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
	//			M�TO_DOS P�BLICOS DE LA CLASE				        
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

	/**
	* M�todo que modifica el valor de una l�nea dada, y si la l�nea 
	* estaba inactiva, la activa.
	* @param line L�nea a la que se le actualizar� el valor.
	* @param value Nuevo valor que se le asignar� a la l�nea.
	*/
	public void setValue(int line, int value) {
		// Creo un objeto conversor de formatos
		XfvhdlBinaryDecimal converter = new XfvhdlBinaryDecimal();
		// Modifico el valor de la l�nea
		vector[line][1] = converter.toBinary(value, N);
		if (!isActive(line)) {
			vector[line][3] = new Boolean(true);
		}
	}

	/**
	* M�todo que modifica el peso de una l�nea dada, y si la l�nea estaba 
	* inactiva, la activa.
	*/
	public void setWeight(int line, int weight) {
		// Creo un objeto conversor de formatos
		XfvhdlBinaryDecimal converter = new XfvhdlBinaryDecimal();
		// Modifico el peso de la l�nea
		vector[line][2] = converter.toBinary(weight, K);
		if (!isActive(line)) {
			vector[line][3] = new Boolean(true);
		}
	}

	/**
	* M�todo que obtiene el valor de una l�nea dada.
	*/
	public String getValue(int line) {
		return (String) vector[line][1];
	}

	/**
	* M�todo que obtiene el peso de una l�nea dada.
	*/
	public String getWeight(int line) {
		return (String) vector[line][2];
	}

	/**
	* M�todo para obtener una l�nea de la estructura (solo nombre).
	*/
	public String getLine(int line) {
		String l = new String();

		l = getBinaryData(line);

		return l;
	}

	/**
	* M�todo para obtener una l�nea de la estructura (nombre,valor y peso).
	*/
	public String getLineComplete(int line) {
		String l = new String();

		l = getBinaryData(line) + getValue(line) + getWeight(line);

		return l;
	}

	/**
	* M�todo para obtener el estado de una l�nea.
	*/
	public boolean isActive(int line) {
		Object o;
		Boolean b;

		o = vector[line][3];
		b = (Boolean) o;

		return b.booleanValue();
	}

	/**
	* M�todo para imprimir por pantalla el contenido de la estructura.
	*/
	public void toScreen() {
		for (int i = 0; i < longitud; i++)
			System.out.println(
				vector[i][0]
					+ " "
					+ vector[i][1]
					+ " "
					+ vector[i][2]
					+ " "
					+ vector[i][3]
					+ " ");
	}

	/**
	* M�todo para obtener el n�mero de l�neas de la estructura.
	*/
	public int getLength() {
		return longitud;
	}

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
	//			  M�TO_DOS PRIVADOS DE LA CLASE				        
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

	/**
	* M�todo privado para obtener el valor binario de una l�nea.
	*/
	private String getBinaryData(int line) {
		return (String) vector[line][0];
	}

	private void getMfUsed(Relation left, Relation right, int nrule) {
		if (left.getKind() == Relation.IS
			&& (right == null || right.getKind() == Relation.IS)) {
			// ********** Trato la parte izquierda ***********
			int pos_v = 0, pos_mf = 0;
			Variable v = left.getVariable();
			ParamMemFunc mf = null;
			try {
			 mf = (ParamMemFunc) left.getMembershipFunction();
			} catch(Exception ex) {
			 new XfvhdlError(31);
			}

			// averiguo de que variable se trata
			boolean enc = false;
			for (int kk = 0; kk < var_in.length && !enc; kk++) {
				if (var_in[kk].equals(v.getName())) {
					pos_v = kk;
					enc = true;
				}
			}

			// averiguo de que mf se trata
			enc = false;
			ParamMemFunc tmp;
			for (int kk = 0;
				kk < inOrderMfInput[pos_v].getInOrderParamMemFunc().size()
					&& !enc;
				kk++) {
				tmp =
					(ParamMemFunc) inOrderMfInput[pos_v]
						.getInOrderParamMemFunc()
						.get(
						kk);
				if (mf != null && tmp.equals(mf.getLabel())) {
					pos_mf = kk;
					enc = true;
				}
			}

			// guardo la informaci�n en mfUsed
			mfUsed[pos_v] = pos_mf;

			// ****** Trato la parte derecha, si es distinta de null *****		
			if (right != null) {
				pos_v = 0;
				pos_mf = 0;
				v = right.getVariable();
				try {
				 mf = (ParamMemFunc) right.getMembershipFunction();
				} catch(Exception ex) {
				 new XfvhdlError(31);
				}
				
				// averiguo de que variable se trata
				enc = false;
				for (int kk = 0; kk < var_in.length && !enc; kk++) {
					if (var_in[kk].equals(v.getName())) {
						pos_v = kk;
						enc = true;
					}
				}

				// averiguo de que mf se trata
				enc = false;
				for (int kk = 0;
					kk < inOrderMfInput[pos_v].getInOrderParamMemFunc().size()
						&& !enc;
					kk++) {
					tmp =
						(ParamMemFunc) inOrderMfInput[pos_v]
							.getInOrderParamMemFunc()
							.get(
							kk);
					if (mf != null && tmp.equals(mf.getLabel())) {
						pos_mf = kk;
						enc = true;
					}
				}

				// guardo la informaci�n en mfUsed
				mfUsed[pos_v] = pos_mf;
			}
		} else if (
			left.getKind() == Relation.AND && right.getKind() == Relation.IS) {
			// ***** Trato la parte derecha que es de tipo IS *****
			int pos_v = 0, pos_mf = 0;
			Variable v = right.getVariable();
			ParamMemFunc mf = null;
			try {
			 mf = (ParamMemFunc) right.getMembershipFunction();
			} catch(Exception ex) {
			 new XfvhdlError(31);
			}

			// averiguo de que variable se trata
			boolean enc = false;
			for (int kk = 0; kk < var_in.length && !enc; kk++) {
				if (var_in[kk].equals(v.getName())) {
					pos_v = kk;
					enc = true;
				}
			}

			// averiguo de que mf se trata
			enc = false;
			ParamMemFunc tmp;
			for (int kk = 0;
				kk < inOrderMfInput[pos_v].getInOrderParamMemFunc().size()
					&& !enc;
				kk++) {
				tmp =
					(ParamMemFunc) inOrderMfInput[pos_v]
						.getInOrderParamMemFunc()
						.get(
						kk);
				if (mf != null && tmp.equals(mf.getLabel())) {
					pos_mf = kk;
					enc = true;
				}
			}

			// guardo la informaci�n en mfUsed
			mfUsed[pos_v] = pos_mf;

			// ******* Llamo recursivamente a esta funcion con **** 
			// ******* la parte izquierda                      ****
			// ******* es de tipo AND. ****************************
			Relation newLeft = left.getLeftRelation();
			Relation newRight = left.getRightRelation();
			getMfUsed(newLeft, newRight, nrule);
		} else {
			// Informo de que la regla nrule no es del tipo
			// A & B & C ... ==> Z
			new XfvhdlError(10, "" + nrule);
			return;
		}

	}

	private double calculateWeight(int pos_c) {
		ParamMemFunc p;
		//Parameter[] par;
		double universeRange = 0, peso = 0;
		p = inOrderMfOutput.getParamMemFunc(pos_c);
		universeRange = p.universe().range();
		//par = p.parameter;

		try {
			peso = p.basis() / universeRange;
		} catch (Exception e) {
			XfvhdlError err = new XfvhdlError(0);
			err.show();
			return 0;
		}

        // Versi�n antigua que calcula la anchura de la mf
        // de forma manual.
		/*
		if(p.name.equalsIgnoreCase("bell")){
			peso = par[1].get() / universeRange;     
		}
		else{
		
		 if (par.length > 0) {
		    mfMax = par[0].get();
		    mfMin = mfMax;
		    for (int i = 0; i < par.length; i++) {
		       if (par[i].get() > mfMax)
		          mfMax = par[i].get();
		       if (par[i].get() < mfMin)
		          mfMin = par[i].get();
		    }
		 }
		
		 if (universeRange != 0)
		    peso = (mfMax - mfMin) / universeRange;
		}
		*/

		return peso;
	}

} // Fin de la clase.
